package satir.framework.command;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Scanner;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import satir.framework.JSONIOPerformer;
import satir.framework.JSONIOPerformer.JSONIOPerformerJSONDecodeException;
import satir.framework.JSONIOPerformer.JSONIOPerformerReadException;
import satir.framework.LazyInitiator;

/**
 * The Class RegularCommandInterpreter.
 * @version 3.0 April 2012
 * @author Creative Family
 */
public class RegularCommandInterpreter {

    /**
     * Instantiates a new regular command interpreter.
     */
    protected RegularCommandInterpreter() {

    }

    /**
     * To display and execute the command referring to specific command set
     * defined in .config files.
     * @param setKey
     *            - the key of command set
     */
    public static void execute(final String setKey) {
	execute(setKey, null);
    }

    /**
     * Execute.
     * @param setKey the set key
     * @param state the state
     */
    public static void execute(final String setKey, final State state) {
	ArrayList<RegularCommand> commands = retrieveCommands(setKey);

	int commandIndex = 0;
	do {

	    // Show the available functions
	    showCommandsInstructions(commands);

	    // Retrieve the input
	    commandIndex = getCommandSelection();

	    // Consider execution
	    if (isInRange(commands, commandIndex)) {
		RegularCommand command = commands.get(commandIndex);
		if (state != null) {
		    command.execute(state);
		} else {
		    command.execute();
		}
	    }

	    if (isInvalid(commands, commandIndex)) {
		System.out.println("Invalid input!");
	    }

	} while (isInvalid(commands, commandIndex));
    }

    /**
     * Gets the command selection.
     *
     * @return the command selection
     */
    protected static int getCommandSelection() {
	Scanner scanner = new Scanner(System.in);
	int commandIndex;
	commandIndex = Integer.parseInt(scanner.next()) - 1;
	return commandIndex;
    }

    /**
     * Checks if is invalid.
     *
     * @param commands the commands
     * @param commandIndex the command index
     * @return true, if is invalid
     */
    protected static boolean isInvalid(final ArrayList<RegularCommand> commands,
	    final int commandIndex) {
	return !isInRange(commands, commandIndex);
    }

    /**
     * Checks if is in range.
     * @param commands
     *            the commands
     * @param commandIndex
     *            the command index
     * @return true, if is in range
     */
    protected static boolean isInRange(final ArrayList<RegularCommand> commands,
	    final int commandIndex) {
	return commandIndex >= 0 && commandIndex < commands.size();
    }

    /**
     * Show commands instructions.
     *
     * @param commands the commands
     */
    protected static void showCommandsInstructions(
	    final ArrayList<RegularCommand> commands) {
	System.out.println("Please indicate your selection.");
	for (int i = 0; isInRange(commands, i); i++) {
	    RegularCommand command = commands.get(i);
	    System.out.println("[" + (i + 1) + "] " + command.getInstruction());
	}
    }

    /**
     * Retrieve commands.
     * @param setKey
     *            the set key
     * @return the array list
     */
    protected static ArrayList<RegularCommand> retrieveCommands(final String setKey) {
	ArrayList<RegularCommand> commands = new ArrayList<RegularCommand>();
	try {
	    // Read Game configuration
	    JSONIOPerformer satirGameConfigIO;
	    satirGameConfigIO = new JSONIOPerformer("config/SatirGame.config");
	    JSONObject satirGameConfig = satirGameConfigIO.retrieve();
	    JSONArray implementations = satirGameConfig
		    .getJSONArray("implementation");

	    // Retrieve implementation details
	    Hashtable<String, Object> commandSetRoot = getImplementation(
		    setKey, implementations);
	    if (commandSetRoot.isEmpty()) {
		throw new RegularCommandInterpreterImplementationNotFoundException(
			setKey);
	    }

	    JSONArray commandSet = (JSONArray) commandSetRoot
		    .get("implementation");

	    for (int i = 0; i < commandSet.length(); i++) {
		JSONObject data = (JSONObject) commandSet.get(i);
		String key = data.getString("key");
		RegularCommand command = (RegularCommand) LazyInitiator.get(
			key, key, true);
		commands.add(command);
	    }

	} catch (JSONIOPerformerReadException e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	} catch (JSONIOPerformerJSONDecodeException e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	} catch (JSONException e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	} catch (RegularCommandInterpreterImplementationNotFoundException e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	}

	return commands;
    }

    /**
     * Gets the implementation.
     * @param key
     *            the key
     * @param implementations
     *            the implementations
     * @return the implementation
     */
    protected static Hashtable<String, Object> getImplementation(final String key,
	    final JSONArray implementations) {
	Hashtable<String, Object> implementation = new Hashtable<String, Object>();
	for (int i = 0; i < implementations.length(); i++) {
	    try {
		JSONObject node = implementations.getJSONObject(i);
		String nodeKey = node.getString("key");
		if (nodeKey.equals(key)) {
		    // Retrieve all fields
		    String[] fields = JSONObject.getNames(node);
		    for (int j = 0; j < fields.length; j++) {
			String field = fields[j];
			implementation.put(field, node.get(field));
		    }
		    break;
		} else if (node.has("implementation")) {
		    // Search child
		    implementation = getImplementation(key,
			    node.getJSONArray("implementation"));
		    if (!implementation.isEmpty()) {
			// Add parent fields
			String[] fields = JSONObject.getNames(node);
			for (int j = 0; j < fields.length; j++) {
			    String field = fields[j];
			    // Add if no conflict
			    if (!field.equals("implementation")
				    && !implementation.containsKey(field)) {
				implementation.put(field, node.get(field));
			    }
			}
			break;
		    }
		}
	    } catch (JSONException e) {
		continue;
	    }
	}
	return implementation;
    }

    /**
     * The Class RegularCommandInterpreterImplementationNotFoundException.
     */
    public static class RegularCommandInterpreterImplementationNotFoundException
	    extends Exception {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = -8048500651606507661L;

	/**
	 * Instantiates a new regular command interpreter implementation not
	 * found exception.
	 * @param key
	 *            the key
	 */
	public RegularCommandInterpreterImplementationNotFoundException(
		final String key) {
	    super(
		    "ERROR: The required componment not found in this verion. Reference: command set key ["
			    + key + "]");
	}
    }
}
